﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Serialization;

namespace Toolkit.Web.Helpers
{
    /// <summary>
    /// Supported methods of Xml serialization and deserialization.
    /// </summary>
    public enum XmlSerializationMethod
    {
        DataContractSerializer = 0,
        XmlSerializer
    }

    /// <summary>
    /// Xml Serialization Helper.
    /// Serializes DataContract objects into Xml strings and Deserializes Xml strings back into c# object.
    /// 
    /// To utilize this class, the object must be marked as serializable in the following manner:
    /// [DataContract] public class foo { ... [DataMember] bar { get; set; } ... }
    /// </summary>
    public static class XmlSerialization
    {
        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object which will be serialized.</typeparam>
        /// <param name="value">Object to serialize.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize<T>(T value)
        {
            return Serialize((object)value, typeof(T));
        }

        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object which will be serialized.</typeparam>
        /// <param name="value">Object to serialize.</param>
        /// <param name="method">Method of serialization to use.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize<T>(T value, XmlSerializationMethod method)
        {
            return Serialize((object)value, typeof(T), method);
        }

        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object which will be serialized.</typeparam>
        /// <param name="value">Object to serialize.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize<T>(T value, Encoding encoding)
        {
            return Serialize((object)value, typeof(T), default(XmlSerializationMethod), encoding);
        }

        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object which will be serialized.</typeparam>
        /// <param name="value">Object to serialize.</param>
        /// <param name="method">Method of serialization to use.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize<T>(T value, XmlSerializationMethod method, Encoding encoding)
        {
            return Serialize((object)value, typeof(T), method, encoding);
        }

        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="value">Object to serialize.</param>
        /// <param name="type">Type of object which will be serialized.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize(object value, Type type)
        {
            return Serialize(value, type, default(XmlSerializationMethod));
        }

        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="value">Object to serialize.</param>
        /// <param name="type">Type of object which will be serialized.</param>
        /// <param name="method">Method of serialization to use.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize(object value, Type type, XmlSerializationMethod method)
        {
            return Serialize(value, type, method, null);
        }

        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="value">Object to serialize.</param>
        /// <param name="type">Type of object which will be serialized.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize(object value, Type type, Encoding encoding)
        {
            return Serialize(value, type, default(XmlSerializationMethod), encoding);
        }

        /// <summary>
        /// Serialize an object into a Xml string (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="value">Object to serialize.</param>
        /// <param name="type">Type of object which will be serialized.</param>
        /// <param name="method">Method of serialization to use.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Serialized object as a Xml string.</returns>
        public static string Serialize(object value, Type type, XmlSerializationMethod method, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.Default;
            }

            if (method == XmlSerializationMethod.DataContractSerializer)
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    DataContractSerializer serializer = new DataContractSerializer(type);
                    serializer.WriteObject(memoryStream, value);
                    return encoding.GetString(memoryStream.ToArray());
                }
            }
            else if (method == XmlSerializationMethod.XmlSerializer)
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    XmlSerializer serializer = new XmlSerializer(type);
                    serializer.Serialize(memoryStream, value);
                    return encoding.GetString(memoryStream.ToArray());
                }
            }
            else
            {
                throw new ArgumentException("Unknown serialization method.");
            }
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object in which the string will be deserialized to.</typeparam>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <returns>Correctly typed deserialized object.</returns>
        public static T Deserialize<T>(string xml)
        {
            return (T)Deserialize(xml, typeof(T), default(XmlSerializationMethod));
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object in which the string will be deserialized to.</typeparam>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Correctly typed deserialized object.</returns>
        public static T Deserialize<T>(string xml, Encoding encoding)
        {
            return (T)Deserialize(xml, typeof(T), default(XmlSerializationMethod), encoding);
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object in which the string will be deserialized to.</typeparam>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <param name="method">Method of deserialization to use.</param>
        /// <returns>Correctly typed deserialized object.</returns>
        public static T Deserialize<T>(string xml, XmlSerializationMethod method)
        {
            return (T)Deserialize(xml, typeof(T), method, null);
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <typeparam name="T">Type of object in which the string will be deserialized to.</typeparam>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <param name="method">Method of deserialization to use.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Correctly typed deserialized object.</returns>
        public static T Deserialize<T>(string xml, XmlSerializationMethod method, Encoding encoding)
        {
            return (T)Deserialize(xml, typeof(T), method, encoding);
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <param name="type">Type of object in which the string will be deserialized to.</param>
        /// <returns>Deserialized object.</returns>
        public static object Deserialize(string xml, Type type)
        {
            return Deserialize(xml, type, default(XmlSerializationMethod));
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <param name="type">Type of object in which the string will be deserialized to.</param>
        /// <param name="method">Method of deserialization to use.</param>
        /// <returns>Deserialized object.</returns>
        public static object Deserialize(string xml, Type type, XmlSerializationMethod method)
        {
            return Deserialize(xml, type, method, null);
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <param name="type">Type of object in which the string will be deserialized to.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Deserialized object.</returns>
        public static object Deserialize(string xml, Type type, Encoding encoding)
        {
            return Deserialize(xml, type, default(XmlSerializationMethod), encoding);
        }

        /// <summary>
        /// Deserialize a Xml string back into a strongly typed object (object and properties must be marked with as DataContract and DataMember attributes).
        /// </summary>
        /// <param name="Xml">Xml string to be deserialized.</param>
        /// <param name="type">Type of object in which the string will be deserialized to.</param>
        /// <param name="method">Method of deserialization to use.</param>
        /// <param name="encoding">Encoding to use during serialization.</param>
        /// <returns>Deserialized object.</returns>
        public static object Deserialize(string xml, Type type, XmlSerializationMethod method, Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.Default;
            }

            if (method == XmlSerializationMethod.DataContractSerializer)
            {
                using (MemoryStream memoryStream = new MemoryStream(encoding.GetBytes(xml)))
                {
                    DataContractSerializer serializer = new DataContractSerializer(type);
                    return serializer.ReadObject(memoryStream);
                }
            }
            else if (method == XmlSerializationMethod.XmlSerializer)
            {
                using (MemoryStream memoryStream = new MemoryStream(encoding.GetBytes(xml)))
                {
                    XmlSerializer serializer = new XmlSerializer(type);
                    return serializer.Deserialize(memoryStream);
                }
            }
            else
            {
                throw new ArgumentException("Unknown serialization method.");
            }
        }
    }
}
